package han.chensing.latos.common.level.content.timeLine;

import han.chensing.latos.common.level.content.timeLine.events.TimeLineEvent;
import han.chensing.latos.common.level.content.timeLine.events.TimeLineMotionEvent;
import han.chensing.latos.common.level.content.timeLine.frame.BaseFrame;
import han.chensing.latos.common.level.content.timeLine.frame.FrameEditor;
import han.chensing.latos.common.level.content.timeLine.frame.FrameGeneral;
import han.chensing.latos.common.level.content.timeLine.frame.GeneralEventTypes;
import han.chensing.latos.common.level.content.timeLine.motion.ValueMotion;
import han.chensing.latos.common.level.util.FrameUtil;

import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@SuppressWarnings("Java8ListSort")
public class TimeLineEventSet<E extends TimeLineEvent<F>, F extends BaseFrame<?>> implements Serializable {

    @Serial private static final long serialVersionUID = -564441435836337336L;

    protected Enum<?> type;
    protected List<E> timeLineEvents;
    protected List<F> keyFrames;

    public TimeLineEventSet(Enum<?> type){
        this.type= type;
        this.keyFrames=new ArrayList<>();
        this.timeLineEvents =new ArrayList<>();
    }

    @SuppressWarnings("unchecked")
    public void makeEditorKeyFrame(float time){
        FrameEditor aspectFrame = FrameUtil.getEditorAspectFrame((TimeLineEventSet<?, FrameEditor>) this, time);
        FrameEditor frame=new FrameEditor(aspectFrame);
        frame.setTime(time);
        pushNewFrame((F) frame);
    }

    @SuppressWarnings("unchecked")
    public void makeLineMoveGeneralKeyFrame(float time){
        FrameGeneral aspectFrame= FrameUtil.getGeneralAspectFrame((TimeLineEventSet<TimeLineMotionEvent,FrameGeneral>) this,time);
        FrameGeneral frame=new FrameGeneral(aspectFrame);
        frame.setTime(time);
        pushNewFrame((F) frame);
    }

    @SuppressWarnings("unchecked")
    public void makeLineMoveGeneralEvent(float time){
        FrameGeneral[] keyFramesBothSidesByTime = FrameUtil.getKeyFramesBothSidesByTime((List<FrameGeneral>) keyFrames, time);
        if (keyFramesBothSidesByTime==null) return;
        ValueMotion valueMotion = ValueMotion.makeLineAnimation(keyFramesBothSidesByTime, (GeneralEventTypes) type);
        TimeLineMotionEvent timeLineMotionEvent = TimeLineMotionEvent.wrapEvent(keyFramesBothSidesByTime, valueMotion);
        pushNewEvent((E) timeLineMotionEvent);
        deleteNearestKeyFrame(keyFramesBothSidesByTime[0].getTime());
        deleteNearestKeyFrame(keyFramesBothSidesByTime[1].getTime());
    }

    @SuppressWarnings("unchecked")
    public void deleteLineMoveEvent(float time){
        try {
            TimeLineMotionEvent nowLineEvent = FrameUtil.getNowLineEvent(time, (TimeLineEventSet<TimeLineMotionEvent,FrameGeneral>) this);
            if (nowLineEvent == null) return;
            getTimeLineEvents().remove((E) nowLineEvent);
        }catch (ClassCastException e){
            throw new ClassCastException("The content of this TimeLineEventSet<> should be TimeLineLineMoveEvent");
        }
    }

    public void deleteNearestKeyFrame(float time){
        F nearestKeyFrame = FrameUtil.getNearestKeyFrame(time, this);
        deleteKeyFrame(nearestKeyFrame);
    }

    public void pushNewEvent(E timeLineEvent){
        timeLineEvents.add(timeLineEvent);
        Collections.sort(timeLineEvents,(a,b)->
                (int) ((a.getFromTime()-b.getToTime())*1000f));
    }

    public void pushNewFrame(F frame){
        keyFrames.add(frame);
        Collections.sort(keyFrames,(a,b)->(int) (a.getTime()*1000f-b.getTime()*1000f));
    }

    public void deleteKeyFrame(F frame){
        keyFrames.remove(frame);
    }

    @SuppressWarnings("unchecked")
    public float getValueDirectly(float absoluteTime, GeneralEventTypes generalEventTypes){
        FrameGeneral aspectFrame;
        try {
            aspectFrame = FrameUtil.getGeneralAspectFrame((TimeLineEventSet<TimeLineMotionEvent,FrameGeneral>) this, absoluteTime);
        }catch (ClassCastException classCastException){
            throw new ClassCastException("The content of this TimeLineEventSet<> should be TimeLineMotionEvent");
        }
        return aspectFrame.getValueFromSingleEventType();
    }

    public List<E> getTimeLineEvents() {
        return timeLineEvents;
    }

    public void setTimeLineEvents(List<E> timeLineEvents) {
        this.timeLineEvents = timeLineEvents;
    }

    public List<F> getKeyFrames() {
        return keyFrames;
    }

    public void setKeyFrames(List<F> keyFrames) {
        this.keyFrames = keyFrames;
    }

    public Enum<?> getType() {
        return type;
    }

    public void setType(Enum<?> type) {
        this.type = type;
    }
}
